#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "maz_cpnt_debugcmd.h"

static int dlvl = DINF;

#define HISTORY_COUNT                       (8)
#define HISTORY_PREV(n)                     n = ((n >= HISTORY_COUNT - 1) ? 0 : (n - 1))
#define HISTORY_NEXT(n)                     n = ((n >= HISTORY_COUNT - 1) ? 0 : (n + 1))
uint8_t history_cmd[HISTORY_COUNT][MAZCPNT_CMD_MAX_STR_LEN];
int success_count = 0;                          // 命令执行成功的次数计数
int base = 0;

/*---------------------------------------------------------------------------------------------------------------------
 * 函数声明
 *-------------------------------------------------------------------------------------------------------------------*/
void __io_putchar(char c);
int debugcmd_prompt(void);
int debugcmd_logo(void);
int debugcmd_register(void);
int debugcmd_register(void);
int debugcmd_uart_mode(void);

/*---------------------------------------------------------------------------------------------------------------------
 * 主要业务
 *-------------------------------------------------------------------------------------------------------------------*/

/**
 * @brief 主函数
 */
int main(int argc, char **argv)
{
    int ret = 0;
    char cmd[MAZCPNT_CMD_MAX_STR_LEN];              // 用户缓存命令的临时字符数组
    uint32_t fill = 0;                              // 串口接收队列中有效数据长度
    uint32_t length = 0;                            // 命令长度
    char vt100[4] = { 0 };                          // 暂存解析到的VT100控制码的值
    char match_cmd[MAZCPNT_CMD_MAX_STR_LEN];
    int match_length = 0;

    /**
     * offset = 0 表示当前命令
     * offset = 1 表示上一条命令
     * offset = 2 表示上上条命令
     * offset 的取值范围为 [0,HISTORY_COUNT)
     * 数学中方括号表示包含, 小括号表示不包含, 因此取值范围为 0 ~ HISTORY_COUNT-1
     */
    int offset = 0;                                 // 历史命令的相对于当前命令的偏移值

    MAZ_CPNT_debugcmd_init();
    debugcmd_register();
    debugcmd_uart_mode();
    debugcmd_logo();
    debugcmd_prompt();

    memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
    memset(match_cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);

    while (1)
    {
        /* 一个字符一个字符的取出数据 */
        cmd[length] = getchar();

        /* 字符回显, 针对没有输入情况下按退格键和TAB键做特殊处理 */
        if (length == 0 && (cmd[length] == '\b' || cmd[length] == '\t'))
        {
            /* 终端还没有输入字符, 此时按TAB和退格输出提示音 */
            cmd[length] = '\0';
            length = 0;
            __io_putchar('\a');
            continue;
        }
        else if (cmd[length] == '\t')
        {
            /* 终端已经存在部分字符, 按TAB键将尝试做自动补全处理 */
            cmd[length] = '\0';
            ret = MAZ_CPNT_debugcmd_automatic_completion(cmd, match_cmd, &match_length);

            /* 根据当前的场景, 自动补全有三种情况 */
            /**
             * 情况1: MAZRET_ENEWLINE -- 有多个字符串匹配, 则补全到字符串差异位置
             * 情况2: MAZRET_EAUTO ----- 只有一个字符串匹配, 则补全这个字符串, 并且追加一个空格
             * 情况3: MAZRET_ERING ----- 没有任何一个字符串匹配, 说明当前输入的内容有误
             */
            if (MAZRET_ENEWLINE == ret)
            {
                /* 打印新的一行命令提示符 */
                debugcmd_prompt();

                /* 如果当前输入的内容没有到多个匹配的字符串的差异位置则补全到差异位置 */
                if (match_length)
                {
                    strcat(cmd, match_cmd);
                    length += match_length;
                }

                printf("%s", cmd);
                fflush(stdout);
            }
            else if (MAZRET_EAUTO == ret)
            {
                /* 只有一个命令包含, 则自动补全这个命令 */
                if (match_length)
                {
                    strcat(cmd, match_cmd);
                    length += match_length;
                    printf("%s", match_cmd);
                    fflush(stdout);
                }
            }
            else if (MAZRET_ERING == ret)
            {
                /* 命令输入错误, 输出提示音 */
                printf("\a");
                fflush(stdout);
            }

            continue;
        }
        else if (cmd[length] == '\033')    // VT100 '\033'
        {
            /* 发现是VT100控制码, 则继续取出后面的两个字符 */
            vt100[0] = getchar();
            vt100[1] = getchar();
            if (vt100[0] == '[')
            {
                /* 判断是否是上下键 */
                if (vt100[1] == 'A' || vt100[1] == 'B')
                {
                    int i = 0;
                    int index = 0;

                    /**
                     * 上键处理方式
                     * 需要判断是否已经回溯到了最后一条命令了.
                     * 最后一条命令有两种场景:
                     * (说明: 以MAZAPP_DEBUGCMD_HISTORY_COUNT=8举例,最多缓存8条历史命令)
                     *      场景1: 已经按了7次上键了, 此时 offset 的值为 7, 再按上也不处理了
                     *      场景2: 系统启动后仅输入了2条命令, 因此最多按2次上键, 再按上也不处理了
                     */
                    if ((vt100[1] == 'A') && (offset < (HISTORY_COUNT - 1))
                            && (offset < success_count))
                    {
                        /* 从最新输入行切换到历史命令时, 暂存目前的输入内容*/
                        if (0 == offset)
                        {
                            /* 将最后的 0x1b 换码符清掉, 否则长度对应不上 */
                            cmd[length] = '\0';
                            memset(history_cmd[base], 0, MAZCPNT_CMD_MAX_STR_LEN);
                            memcpy(history_cmd[base], cmd, strlen(cmd));
                        }

                        /* 每次按上键, offset加1 */
                        offset++;

                        /* 根据偏移值计算历史命令的下标 */
                        index = base - offset;

                        /* 由于是环形数组, 因此还需要判断是否回环 */
                        if (index < 0)
                        {
                            index = index + HISTORY_COUNT;
                        }

                        /* 先清除当前的输出, 先退格, 然后输出空格清除, 最后退一格 */
                        for (i = 0; i < length; i++)
                        {
                            printf("\b \b");
                            fflush(stdout);
                        }
                        memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);

                        /* 取出下标对应的历史命令 */
                        length = strlen((char *) history_cmd[index]);
                        memcpy(cmd, history_cmd[index], length);

                        /* 打印历史命令 */
                        printf("\r");
                        debugcmd_prompt();
                        printf("%s", cmd);
                        fflush(stdout);
                    }
                    else if ((vt100[1] == 'B') && (offset > 0))
                    {
                        offset--;

                        index = base - offset;
                        if (index < 0)
                        {
                            index = index + HISTORY_COUNT;
                        }

                        for (i = 0; i < length; i++)
                        {
                            printf("\b \b");
                            fflush(stdout);
                        }
                        memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);

                        length = strlen((char *) history_cmd[index]);
                        memcpy(cmd, history_cmd[index], length);

                        printf("\r");
                        debugcmd_prompt();
                        printf("%s", cmd);
                        fflush(stdout);
                    }
                    else
                    {
                        /* 已经切换到了记录的最早的历史命令了, 输出提示音 */
                        printf("\a");
                        fflush(stdout);
                    }
                }
                else if (vt100[1] == '3') /* 判断是否是佩锐 512 退格键 */
                {
                    vt100[2] = getchar();

                    if (vt100[2] == '~')
                    {
                        if (length == 0)
                        {
                            cmd[length] = '\0';
                            length = 0;
                            __io_putchar('\a');
                            continue;
                        }

                        __io_putchar('\b');
                        __io_putchar(' ');
                        __io_putchar('\b');
                        cmd[length] = '\0';
                        if (length > 0)
                        {
                            length--;
                        }
                        continue;
                    }
                }
            }

            cmd[length] = '\0';
            continue;
        }
        else
        {
            __io_putchar(cmd[length]);
        }

        /* 适配 XShell 串口终端 */
        if (cmd[length] == '\r')
        {
            __io_putchar('\n');
        }

        /* 回车换行键处理 */
        if (cmd[length] == '\r' || cmd[length] == '\n')
        {
            cmd[length] = '\0';
            ret = MAZ_CPNT_debugcmd_execute(cmd);

            /* 命令执行成功后, 将命令记录到 history 命令列表 */
            if (MAZRET_OK == ret)
            {
                success_count++;
                memset(history_cmd[base], 0, MAZCPNT_CMD_MAX_STR_LEN);
                memcpy(history_cmd[base], cmd, strlen(cmd));
                HISTORY_NEXT(base);
                offset = 0;
            }
            debugcmd_prompt();
            memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
            length = 0;
            continue;
        }

        if (cmd[length] == '\b') /* 退格键处理 */
        {
            __io_putchar(' ');
            __io_putchar('\b');
            cmd[length] = '\0';
            if (length > 0)
            {
                length--;
            }
        }
        else if (cmd[length] == 0x03) /* CTRL + C 终止信号处理，放弃本次输入 */
        {
            dmsg(DERR, "^C\r\n");
            debugcmd_prompt();
            length = 0;
            continue;
        }
        else
        {
            length++;
        }

        /* 输入命令长度超过 DEBUGCMD 组件约束 */
        if (length >= MAZCPNT_CMD_MAX_STR_LEN)
        {
            memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
            dmsg(DERR, "lenght over, length = %u\r\n", length);
            length = 0;
        }
    }

    return 0;
}

/*---------------------------------------------------------------------------------------------------------------------
 * 内部函数接口
 *-------------------------------------------------------------------------------------------------------------------*/

/**
 * @brief 打印单个字符
 * @retval 错误码
 */
void __io_putchar(char c)
{
    printf("%c", c);
    fflush(stdout);
}

int debugcmd_uart_mode(void)
{
    /**
     * 修改 shell 终端的交互方式
     * -icanon  直接读取, 无需等待enter
     * -echo    不回显
     */
    system("stty -icanon -echo");

    return MAZRET_OK;
}

/**
 * @brief 打印命令提示符
 * @retval 错误码
 */
int debugcmd_prompt(void)
{
    printf(">>> ");
    fflush(stdout);
    return MAZRET_OK;
}

/**
 * @brief 打印启动界面
 * @retval 错误码
 */
int debugcmd_logo(void)
{
    printf("Welcome to the \"debugcmd\" component!\r\n");
    printf("     _      _                                    _ \r\n");
    printf("  __| | ___| |__  _   _  __ _  ___ _ __ ___   __| |\r\n");
    printf(" / _` |/ _ \\ '_ \\| | | |/ _` |/ __| '_ ` _ \\ / _` |\r\n");
    printf("| (_| |  __/ |_) | |_| | (_| | (__| | | | | | (_| |\r\n");
    printf(" \\__,_|\\___|_.__/ \\__,_|\\__, |\\___|_| |_| |_|\\__,_|\r\n");
    printf("                        |___/                      \r\n");
    printf("\r\n");

    printf("Source : https://gitee.com/mazcpnt/debugcmd\r\n");
    printf("Wiki   : https://www.yuque.com/mz8023yt/debugcmd\r\n");
    printf("\r\n");

    printf("debugcmd version v%d.%d.%d(%s %s)\r\n",
           MAZCPNT_DEBUGCMD_MAIN_VER,
           MAZCPNT_DEBUGCMD_SUB_VER,
           MAZCPNT_DEBUGCMD_REV_VER,
           __DATE__, __TIME__);
    printf("Try \"%s\" for help information.\r\n", MAZCPNT_CMD_HELP_COMMAND);
    fflush(stdout);
    return MAZRET_OK;
}

/*---------------------------------------------------------------------------------------------------------------------
 * 命令回调函数定义
 *-------------------------------------------------------------------------------------------------------------------*/

/**
 * @brief i2c 读函数
 * @retval 错误码
 */
int MAZ_CMD_i2c_read(void *cmd, char *param)
{
    uint32_t slave;
    uint32_t reg;

    sscanf(param, "0x%x 0x%x", &slave, &reg);

    printf("i2c read cmd is running\r\n");
    printf("slave = 0x%02x\r\n", slave);
    printf("reg   = 0x%02x\r\n", reg);

    return MAZRET_OK;
}

/**
 * @brief i2c 写函数
 * @retval 错误码
 */
int MAZ_CMD_i2c_write(void *cmd, char *param)
{
    uint32_t slave;
    uint32_t reg;
    uint32_t val;

    sscanf(param, "0x%x 0x%x 0x%x", &slave, &reg, &val);

    printf("i2c write cmd is running\r\n");
    printf("slave = 0x%02x\r\n", slave);
    printf("reg   = 0x%02x\r\n", reg);
    printf("val   = 0x%02x\r\n", val);

    return MAZRET_OK;
}

/**
 * @brief i2c dump函数
 * @retval 错误码
 */
int MAZ_CMD_i2c_dump(void *cmd, char *param)
{
    uint32_t slave;

    sscanf(param, "0x%x", &slave);

    printf("i2c dump cmd is running\r\n");
    printf("slave = 0x%02x\r\n", slave);

    return MAZRET_OK;
}

/**
 * @brief 系统推出函数
 * @retval 错误码
 */
int MAZ_CMD_exit(void *cmd, char *param)
{
    printf("exit.\r\n");
    system("stty icanon echo");
    exit(0);
    return MAZRET_OK;
}

/**
 * @brief 清屏函数
 * @retval 错误码
 */
int MAZ_CMD_clear(void *cmd, char *param)
{
    /* 基于 VT100 控制码实现清屏动作 */
    __io_putchar('\x1b');
    __io_putchar('[');
    __io_putchar('2');
    __io_putchar('J');
    __io_putchar('\x1b');
    __io_putchar('[');
    __io_putchar('H');
    return MAZRET_OK;
}

/**
 * @brief 重启
 * @retval 错误码
 */
int MAZ_CMD_reset(void *cmd, char *param)
{
    MAZ_CMD_clear(cmd, param);
    debugcmd_logo();
    return MAZRET_OK;
}

/**
 * @brief 打印历史命令
 * @retval 错误码
 */
int MAZ_CMD_history(void *cmd, char *param)
{
    int offset = 0;                 // 循环变量
    int oldest_cmd_offset = 0;      // 最老的命令的偏移值
    int index = 0;
    int length = 0;

    oldest_cmd_offset = (success_count >= HISTORY_COUNT) ? (HISTORY_COUNT - 1) : success_count;

    for(offset = oldest_cmd_offset; offset > 0; offset--)
    {
        index = base - offset;
        if (index < 0)
        {
            index = index + HISTORY_COUNT;
        }
        printf("  %d  %s\r\n", success_count - offset + 1, history_cmd[index]);
        fflush(stdout);
    }

    return MAZRET_OK;
}

MAZCPNT_MAIN_CMD i2c        = {"i2c",       "i2c bus debug command."};
MAZCPNT_SUB_CMD  i2c_read   = {" read",     "i2c read <slave> <reg>.",          MAZ_CMD_i2c_read};
MAZCPNT_SUB_CMD  i2c_write  = {"write ",    "i2c wrtie <slave> <reg> <val>.",   MAZ_CMD_i2c_write};
MAZCPNT_SUB_CMD  i2c_dump   = {"dump",      "i2c dump <slave>.",                MAZ_CMD_i2c_dump};
MAZCPNT_MAIN_CMD sysexit    = {" exit",     "exit",                             MAZ_CMD_exit};
MAZCPNT_MAIN_CMD clear      = {"clear ",    "clear",                            MAZ_CMD_clear};
MAZCPNT_MAIN_CMD reset      = {" reset  ",  "reset",                            MAZ_CMD_reset};
MAZCPNT_MAIN_CMD history    = {"history",   "history",                          MAZ_CMD_history};

/**
 * @brief 注册命令函数
 * @retval 错误码
 */
int debugcmd_register(void)
{
    MAZ_CPNT_debugcmd_mcmd_register(&i2c);
    MAZ_CPNT_debugcmd_scmd_register(&i2c, &i2c_read);
    MAZ_CPNT_debugcmd_scmd_register(&i2c, &i2c_write);
    MAZ_CPNT_debugcmd_scmd_register(&i2c, &i2c_dump);
    MAZ_CPNT_debugcmd_mcmd_register(&sysexit);
    MAZ_CPNT_debugcmd_mcmd_register(&clear);
    MAZ_CPNT_debugcmd_mcmd_register(&reset);
    MAZ_CPNT_debugcmd_mcmd_register(&history);
    return MAZRET_OK;
}

